// Package storage provides EROFS (Enhanced Read-Only File System) storage backend implementation
package storage

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/smart-snapshotter/pkg/types"
)

// EROFSStorage provides EROFS-based storage backend
type EROFSStorage struct {
	*BaseBackend
	mountPoint string
	imagePath  string
}

// EROFSConfig represents EROFS-specific configuration
type EROFSConfig struct {
	Config
	MountOptions []string
	ImageSize    int64
	ClusterSize  int32
}

// NewEROFStorage creates a new EROFS storage backend
func NewEROFStorage(config EROFSConfig) (*EROFSStorage, error) {
	base := NewBaseBackend(config.Config)
	if err := base.Initialize(); err != nil {
		return nil, fmt.Errorf("failed to initialize base backend: %w", err)
	}

	// Create EROFS-specific directories
	erofsDirs := []string{"images", "mounts"}
	for _, dir := range erofsDirs {
		path := filepath.Join(base.basePath, dir)
		if err := os.MkdirAll(path, 0755); err != nil {
			return nil, fmt.Errorf("failed to create EROFS directory %s: %w", dir, err)
		}
	}

	storage := &EROFSStorage{
		BaseBackend: base,
		imagePath:   filepath.Join(base.basePath, "images", "erofs.img"),
		mountPoint:  filepath.Join(base.basePath, "mounts", "erofs"),
	}

	// Check if EROFS is supported
	if err := storage.checkEROFSsupport(); err != nil {
		return nil, fmt.Errorf("EROFS not supported: %w", err)
	}

	return storage, nil
}

// checkEROFSsupport checks if EROFS is supported on the system
func (e *EROFSStorage) checkEROFSsupport() error {
	// Check if erofs-utils is available
	if _, err := exec.LookPath("mkfs.erofs"); err != nil {
		return fmt.Errorf("mkfs.erofs not found: %w", err)
	}

	// Check kernel support by trying to load the module
	if err := exec.Command("modprobe", "erofs").Run(); err != nil {
		// Try to check if it's built-in
		if _, err := os.Stat("/sys/module/erofs"); os.IsNotExist(err) {
			return fmt.Errorf("EROFS kernel module not available: %w", err)
		}
	}

	return nil
}

// CreateImage creates a new EROFS image
func (e *EROFSStorage) CreateImage(ctx context.Context, size int64) error {
	// Create mount point
	if err := os.MkdirAll(e.mountPoint, 0755); err != nil {
		return fmt.Errorf("failed to create mount point: %w", err)
	}

	// Create sparse image file
	if err := e.createSparseFile(e.imagePath, size); err != nil {
		return fmt.Errorf("failed to create sparse file: %w", err)
	}

	// Format as EROFS
	if err := e.formatEROFS(ctx); err != nil {
		os.Remove(e.imagePath) // Clean up on failure
		return fmt.Errorf("failed to format EROFS: %w", err)
	}

	return nil
}

// createSparseFile creates a sparse file of the specified size
func (e *EROFSStorage) createSparseFile(path string, size int64) error {
	file, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("failed to create file: %w", err)
	}
	defer file.Close()

	// Seek to the end to set file size (creates sparse file)
	if _, err := file.Seek(size-1, 0); err != nil {
		return fmt.Errorf("failed to seek: %w", err)
	}

	// Write a single byte to allocate the file
	if _, err := file.Write([]byte{0}); err != nil {
		return fmt.Errorf("failed to write: %w", err)
	}

	return nil
}

// formatEROFS formats the image as EROFS
func (e *EROFSStorage) formatEROFS(ctx context.Context) error {
	args := []string{
		"-zlz4hc", // Use LZ4HC compression
		"-C", fmt.Sprintf("%d", e.config.BlockSize), // Cluster size
		"-T", "1234567890", // Fixed timestamp for reproducibility
		e.imagePath,
		e.mountPoint,
	}

	cmd := exec.CommandContext(ctx, "mkfs.erofs", args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("mkfs.erofs failed: %w, output: %s", err, string(output))
	}

	return nil
}

// Mount mounts the EROFS image
func (e *EROFSStorage) Mount(ctx context.Context, targetPath string) error {
	// Create target directory
	if err := os.MkdirAll(targetPath, 0755); err != nil {
		return fmt.Errorf("failed to create target directory: %w", err)
	}

	// Check if already mounted
	if e.isMounted(targetPath) {
		return nil
	}

	// Mount the EROFS image
	args := []string{
		"-t", "erofs",
		"-o", "ro", // Read-only
	}

	if len(e.config.MountOptions) > 0 {
		args = append(args, "-o", strings.Join(e.config.MountOptions, ","))
	}

	args = append(args, e.imagePath, targetPath)

	cmd := exec.CommandContext(ctx, "mount", args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("mount failed: %w, output: %s", err, string(output))
	}

	return nil
}

// Unmount unmounts the EROFS image
func (e *EROFSStorage) Unmount(ctx context.Context, targetPath string) error {
	if !e.isMounted(targetPath) {
		return nil
	}

	cmd := exec.CommandContext(ctx, "umount", targetPath)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("umount failed: %w, output: %s", err, string(output))
	}

	return nil
}

// isMounted checks if a path is mounted
func (e *EROFSStorage) isMounted(path string) bool {
	// Check /proc/mounts
	data, err := os.ReadFile("/proc/mounts")
	if err != nil {
		return false
	}

	content := string(data)
	return strings.Contains(content, path)
}

// StoreFile stores a file in EROFS (requires remounting)
func (e *EROFSStorage) StoreFile(ctx context.Context, hash string, data []byte) error {
	// EROFS is read-only, so we need to handle this differently
	// We can store in a writable overlay or staging area
	stagingPath := filepath.Join(e.basePath, "staging", hash)

	// Create staging directory
	dir := filepath.Dir(stagingPath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create staging directory: %w", err)
	}

	// Write to staging area
	if err := os.WriteFile(stagingPath, data, 0644); err != nil {
		return fmt.Errorf("failed to write to staging: %w", err)
	}

	// Update statistics
	e.UpdateStats(int64(len(data)), 1, 0, 0)

	return nil
}

// LoadFile loads a file from EROFS
func (e *EROFSStorage) LoadFile(ctx context.Context, hash string) ([]byte, error) {
	// Try to load from mounted EROFS first
	erofsPath := filepath.Join(e.mountPoint, hash)
	if data, err := os.ReadFile(erofsPath); err == nil {
		return data, nil
	}

	// Try staging area
	stagingPath := filepath.Join(e.basePath, "staging", hash)
	if data, err := os.ReadFile(stagingPath); err == nil {
		return data, nil
	}

	return nil, types.ErrStorageNotFound
}

// DeleteFile deletes a file (from staging area)
func (e *EROFSStorage) DeleteFile(ctx context.Context, hash string) error {
	stagingPath := filepath.Join(e.basePath, "staging", hash)

	info, err := os.Stat(stagingPath)
	if err != nil {
		if os.IsNotExist(err) {
			return types.ErrStorageNotFound
		}
		return fmt.Errorf("failed to stat file: %w", err)
	}

	if err := os.Remove(stagingPath); err != nil {
		return fmt.Errorf("failed to delete file: %w", err)
	}

	// Update statistics
	e.UpdateStats(-info.Size(), -1, 0, 0)

	return nil
}

// FileExists checks if a file exists
func (e *EROFSStorage) FileExists(ctx context.Context, hash string) (bool, error) {
	// Check EROFS
	erofsPath := filepath.Join(e.mountPoint, hash)
	if _, err := os.Stat(erofsPath); err == nil {
		return true, nil
	}

	// Check staging
	stagingPath := filepath.Join(e.basePath, "staging", hash)
	if _, err := os.Stat(stagingPath); err == nil {
		return true, nil
	}

	return false, nil
}

// StoreBlock stores a block (not supported in EROFS)
func (e *EROFSStorage) StoreBlock(ctx context.Context, hash string, data []byte) error {
	return fmt.Errorf("block storage not supported in EROFS backend")
}

// LoadBlock loads a block (not supported in EROFS)
func (e *EROFSStorage) LoadBlock(ctx context.Context, hash string) ([]byte, error) {
	return nil, fmt.Errorf("block storage not supported in EROFS backend")
}

// DeleteBlock deletes a block (not supported in EROFS)
func (e *EROFSStorage) DeleteBlock(ctx context.Context, hash string) error {
	return fmt.Errorf("block storage not supported in EROFS backend")
}

// BlockExists checks if a block exists (not supported in EROFS)
func (e *EROFSStorage) BlockExists(ctx context.Context, hash string) (bool, error) {
	return false, fmt.Errorf("block storage not supported in EROFS backend")
}

// StoreMetadata stores metadata
func (e *EROFSStorage) StoreMetadata(ctx context.Context, key string, metadata map[string]string) error {
	// Store metadata in a separate JSON file
	metadataPath := filepath.Join(e.basePath, "metadata", key+".json")

	dir := filepath.Dir(metadataPath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create metadata directory: %w", err)
	}

	// Convert metadata to JSON and store
	jsonData := fmt.Sprintf("%v", metadata) // Simplified for now
	if err := os.WriteFile(metadataPath, []byte(jsonData), 0644); err != nil {
		return fmt.Errorf("failed to write metadata: %w", err)
	}

	return nil
}

// LoadMetadata loads metadata
func (e *EROFSStorage) LoadMetadata(ctx context.Context, key string) (map[string]string, error) {
	metadataPath := filepath.Join(e.basePath, "metadata", key+".json")

	data, err := os.ReadFile(metadataPath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, types.ErrStorageNotFound
		}
		return nil, fmt.Errorf("failed to read metadata: %w", err)
	}

	// Parse JSON (simplified for now)
	metadata := make(map[string]string)
	metadata["data"] = string(data)

	return metadata, nil
}

// DeleteMetadata deletes metadata
func (e *EROFSStorage) DeleteMetadata(ctx context.Context, key string) error {
	metadataPath := filepath.Join(e.basePath, "metadata", key+".json")

	if err := os.Remove(metadataPath); err != nil {
		if os.IsNotExist(err) {
			return types.ErrStorageNotFound
		}
		return fmt.Errorf("failed to delete metadata: %w", err)
	}

	return nil
}

// GetStats returns EROFS-specific statistics
func (e *EROFSStorage) GetStats(ctx context.Context) (StorageStats, error) {
	baseStats, err := e.BaseBackend.GetStats(ctx)
	if err != nil {
		return StorageStats{}, err
	}

	// Add EROFS-specific stats
	var stat syscall.Statfs_t
	if err := syscall.Statfs(e.mountPoint, &stat); err == nil {
		baseStats.TotalSize = int64(stat.Blocks) * int64(stat.Bsize)
		baseStats.FreeSize = int64(stat.Bavail) * int64(stat.Bsize)
		baseStats.UsedSize = baseStats.TotalSize - baseStats.FreeSize
	}

	return baseStats, nil
}

// Cleanup performs EROFS-specific cleanup
func (e *EROFSStorage) Cleanup(ctx context.Context) error {
	// Unmount if mounted
	if err := e.Unmount(ctx, e.mountPoint); err != nil {
		return fmt.Errorf("failed to unmount: %w", err)
	}

	// Clean up staging area
	stagingDir := filepath.Join(e.basePath, "staging")
	if err := os.RemoveAll(stagingDir); err != nil {
		return fmt.Errorf("failed to clean staging: %w", err)
	}

	// Recreate staging directory
	if err := os.MkdirAll(stagingDir, 0755); err != nil {
		return fmt.Errorf("failed to recreate staging: %w", err)
	}

	return e.BaseBackend.Cleanup(ctx)
}

// Close closes the EROFS storage
func (e *EROFSStorage) Close() error {
	// Unmount all mounted paths
	if err := e.Unmount(context.Background(), e.mountPoint); err != nil {
		return fmt.Errorf("failed to unmount on close: %w", err)
	}

	return nil
}